package com.ll19.dao.jpa;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ll19.dao.GenericDao;

/**
 * This class serves as the Base class for all other DAOs - namely to hold
 * common CRUD methods that they might all use. You should only need to extend
 * this class when your require custom CRUD logic.
 * 
 * <p>
 * To register this class in your Spring context file, use the following XML.
 * 
 * <pre>
 *      &lt;bean id="fooDao" class="org.appfuse.dao.hibernate.GenericDaoJpaHibernate"&gt;
 *          &lt;constructor-arg value="org.appfuse.model.Foo"/&gt;
 *          &lt;property name="sessionFactory" ref="sessionFactory"/&gt;
 *      &lt;/bean&gt;
 * </pre>
 * 
 * 通用增删改查接口的实现类
 * 
 * @author <a href="mailto:bwnoll@gmail.com">Bryan Noll</a>
 * @param <T>
 *            a type variable
 * @param <PK>
 *            the primary key for that type
 */

/**
 * 
 * 注意 spring 3已经不推荐使用JpaTemplate
 * 
 * 所以这里没有配置类似hibernateTemplate 的 JpaTemplate
 * 
 * 也没有使用JpaDaoSupport来实现dao层
 * 
 * 应为JpaDaoSupport多要使用 getJpaTemplate 操作数据库
 * 
 * entityManager可以完成所有 JpaTemplate 完成的内容 并且代码同样简洁
 * 
 * 
 * PersistenceContext方式是适合Spring3.1版本以后的。
 * 
 * 那就是说，3.0.x版本还是只能用extends JpaDaoSupport吗？
 * 
 * 3.1才不推荐使用JpaDaoSupport和JpaTemplate。建议使用@PersistenceContext注入
 * 
 * Spring在3.1之后决定完全支持JPA2标准，准备放弃之前的JpaDaoSupport和JpaTemplate等。也就是说，
 * 以后Spring不再使用JpaTemplate的方式去回调实现JPA的接口， 而是完全采用注解和注入的方式去实现，这样就实现了Spring的完全解耦合。
 * 
 * 
 * @author liukai
 * 
 * @param <T>
 * @param <PK>
 */
public class GenericDaoJpa<T, PK extends Serializable> implements
		GenericDao<T, PK> {
	/**
	 * Log variable for all child classes. Uses LogFactory.getLog(getClass())
	 * from Commons Logging
	 */

	// Commons Logging 通用Log处理，它是一个接口抽象，底层的实现可以自动替换：
	// 如果当前存在log4j,则使用log4j来实现
	// 否则，使用JDK LOG来实现
	// 否则，使用其自身的简单实现
	// 注意：一般我们在程序中会使用Commons log这个接口，这样当程序中没有log4j的jar包时，程序会自动使用JDK的log
	// 例如：
	//
	// package com.wjt276.log;
	// import org.apache.commons.logging.Log;
	// import org.apache.commons.logging.LogFactory;
	// public class CommonsLogTest {
	// public static void main(String[] args) {
	// Log logger = LogFactory.getLog(CommonsLogTest.class);
	// logger.debug("DEBUG信息");
	// logger.info("INFO信息");
	// logger.warn("WARN信息");
	// logger.error("ERROR信息");
	// logger.fatal("FATAL信息");
	// }
	// }
	// 当程序中有log4j的jar包和log4j.properties时，则使用log4j进行日志输出，否则使用jdklog进行日志输出

	protected final Log log = LogFactory.getLog(getClass());

	public static final String PERSISTENCE_UNIT_NAME = "ApplicationEntityManager";

	/**
	 * Entity manager, injected by Spring using @PersistenceContext annotation
	 * on setEntityManager()
	 */
	// 通过注释注入entitymanager
	// persistenceContext 或通过 JPA @PersistenceContext 注释注入到任何 Spring bean 中。
	// 应该是对应persistence.xml中的<persistence-unit name="ApplicationEntityManager"？
	// Ø 一个持久化单元（Persistence Unit）就是关于一组Entity类的命名配置。持久化单元是一个静态的概念。
	// Ø 一个持久化上下文（Persistence
	// Context）就是一个受管的Entity实例的集合。每一个持久化上下文都关联一个持久化单元，持久化上下文不可能脱离持久化单元独立存在。
	@PersistenceContext(unitName = PERSISTENCE_UNIT_NAME)
	private EntityManager entityManager;
	private Class<T> persistentClass;

	/**
	 * Constructor that takes in a class to see which type of entity to persist.
	 * Use this constructor when subclassing or using dependency injection.
	 * 
	 * @param persistentClass
	 *            the class type you'd like to persist
	 */
	public GenericDaoJpa(final Class<T> persistentClass) {
		this.persistentClass = persistentClass;
	}

	/**
	 * Constructor that takes in a class to see which type of entity to persist.
	 * Use this constructor when subclassing or using dependency injection.
	 * 
	 * @param persistentClass
	 *            the class type you'd like to persist
	 * @param entityManager
	 *            the configured EntityManager for JPA implementation.
	 */
	public GenericDaoJpa(final Class<T> persistentClass,
			EntityManager entityManager) {
		this.persistentClass = persistentClass;
		this.entityManager = entityManager;
	}

	public EntityManager getEntityManager() {
		return this.entityManager;
	}

	/**
	 * {@inheritDoc}
	 */
	// @SuppressWarnings("unused")
	// JDK5.0后的新特性，你在使用IDE如eclipse的时候，当你定义了一个变量如int
	// a=0;但是你后面根本就没有使用到这个变量，这一行的前面会有一个黄色的警告标志。
	// 你将鼠标移动到上面会提示“这个变量从未被使用”，你用上面的标注后就没有这个提示了。
	@SuppressWarnings("unchecked")
	public List<T> getAll() {
		return this.entityManager.createQuery(
				"select obj from " + this.persistentClass.getName() + " obj")
				.getResultList();
	}

	/**
	 * 使用HashSet实现的元素不重复的集合，获取所有不重复的记录。 {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public List<T> getAllDistinct() {
		Collection result = new LinkedHashSet(getAll());
		return new ArrayList(result);
	}

	/**
	 * {@inheritDoc}
	 */
	public T get(PK id) {
		T entity = this.entityManager.find(this.persistentClass, id);

		if (entity == null) {
			String msg = "Uh oh, '" + this.persistentClass
					+ "' object with id '" + id + "' not found...";
			log.warn(msg);
			throw new EntityNotFoundException(msg);
		}

		return entity;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean exists(PK id) {
		T entity = this.entityManager.find(this.persistentClass, id);
		return entity != null;
	}

	/**
	 * {@inheritDoc}
	 */
	public T save(T object) {
		return this.entityManager.merge(object);
	}

	/**
	 * {@inheritDoc}
	 */
	public void remove(PK id) {
		this.entityManager.remove(this.get(id));
	}
}
